package com.small.common.manager.redis.impl;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.small.common.manager.redis.RedisClient;
import com.small.common.manager.redis.RedisLockService;
import static com.small.common.constant.RedisConstants.NX;
import static com.small.common.constant.RedisConstants.PX;
import lombok.extern.slf4j.Slf4j;


/**
 * redis实现的分布式锁
 * @author dingyou
 * @version 1.6
 * @date 2018/11/7
 */
@Service
@Slf4j
public class RedisLockServiceImpl implements RedisLockService {
    private final RedisClient redisClient;
    /** 默认的锁超时时间(秒)*/
    private static final int DEFAULT_TIMEOUT = 3;
    /** 默认的锁超时时间单位*/
    private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
    /** 上锁成功时redis的返回参数*/
    private static final String LOCK_OK = "OK";
    /** 解锁成功时redis的返回参数(使用的lua脚本不一样,返回数据可能不一样)*/
    private static final Long UNLOCK_OK = 1L;

    /** 解锁的lua脚本*/
    private static final String SCRIPT_STR = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    public RedisLockServiceImpl(RedisClient redisClient) {
        this.redisClient = redisClient;
    }

    @Override
    public String lock(String key, long expire, TimeUnit timeUnit) {
        Assert.notNull(key, "key不能为空");
        final TimeUnit unit = timeUnit == null ? DEFAULT_TIME_UNIT : timeUnit;
        final String tag = UUID.randomUUID().toString();
        String set = redisClient.set(key, tag, NX, PX, (int)unit.toMillis(expire));
        if(StringUtils.equalsIgnoreCase(LOCK_OK, set)){
            return tag;
        }
        return null;
    }

    @Override
    public String lock(String key, long expire) {
        return lock(key, expire, DEFAULT_TIME_UNIT);
    }

    @Override
    public String lock(String key) {
        return lock(key, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT);
    }

    /**
     * 上锁,一直等到锁定成功
     * @param key
     * @return
     */
    @Override
    public String waitLocked(String key) {
        /*
        这里有尝试过使用订阅key失效/删除的方式来实现，但是实验发现，有时候key删除之后要等十多秒才收到消息.延迟太高
         */
        String tag;
        while((tag = lock(key)) == null){
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                log.error("InterruptedException", e);
            }
        }
        return tag;
    }

    /**
     * 上锁,一直等到锁定成功
     * @param key
     * @param expire
     * @return
     */
    @Override
    public String waitLocked(String key, long expire) {
        String tag;
        while((tag = lock(key, expire)) == null){
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                log.error("InterruptedException", e);
            }
        }
        return tag;
    }

    /**
     * 上锁,一直等到锁定成功
     * @param key
     * @param expire
     * @param timeUnit
     * @return
     */
    @Override
    public String waitLocked(String key, long expire, TimeUnit timeUnit) {
        String tag;
        while((tag = lock(key, expire, timeUnit)) == null){
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                log.error("InterruptedException", e);
            }
        }
        return tag;
    }

    @Override
    public boolean unLock(String key, String lockTag) {
        Object execute = redisClient.execute(jedis -> jedis.eval(SCRIPT_STR, 1, key, lockTag));
        return UNLOCK_OK.equals(execute);
    }
}
